package com.zzyl.nursing.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.constant.Constants;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IAlertRuleService;
import com.zzyl.system.mapper.SysRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 *
 * @author zzyl
 * @date 2025-10-28
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysRoleMapper sysRoleMapper;

    @Autowired
    private IAlertDataService alertDataService;

    @Value("${alertRule.roleAdmin}")
    private String roleAdmin;

    @Value("${alertRule.roleRepair}")
    private String roleRepair;


    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }

    /**
     * 设备数据对应的报警规则校验，将报警数据保存到对应表中
     */
    @Override
    public void alertRuleFilter() {
        // 1.查询所有规则,并判断
        Long statusCount = this.lambdaQuery()
                .eq(AlertRule::getStatus, Constants.STATUS_ENABLE)
                .count();

        if (statusCount == 0) {
            log.error("[报警规则校验]没有查询到任何报警规则，无需处理");
            return;
        }

        // 2.从缓存中查询所有设备的最新数据并判断
        List<Object> lastDeviceList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (CollUtil.isEmpty(lastDeviceList)) {
            log.error("[报警规则校验]没有查询到任何最新数据，无需处理");
            return;
        }

        // 3.数据格式标准化处理(多个集合只能遍历处理)
        ArrayList<DeviceData> deviceDataList = new ArrayList<>();
        lastDeviceList.forEach(lastDevice -> {
            String jsonStr = JSONUtil.toJsonStr(lastDevice);
            List<DeviceData> deviceDataListSmall = JSONUtil.toList(jsonStr, DeviceData.class);
            deviceDataList.addAll(deviceDataListSmall);
        });


        // 4.遍历所有设备上报
        deviceDataList.forEach(deviceData -> {
            // 4.0 超过一小时的数据不进行处理
            LocalDateTime oneHourBefore = LocalDateTime.now().withHour(1);
            LocalDateTime alarmTime = deviceData.getAlarmTime();
            if (ObjectUtil.isNull(alarmTime)) {
                log.error("[报警规则校验]获取最新数据时间错误,请检查");
                return;
            }
            if (alarmTime.isBefore(oneHourBefore)) {
                log.error("[报警规则校验]设备名称：{}，功能：{}，设备ID：{}，数据上报时间太久不再处理", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId());
                return;
            }
            // 4.1 查询上报数据对应的设备同类型产品下全部设备关联的报警规则列表
            List<AlertRule> productAlertRuleList = this.lambdaQuery()
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    .eq(AlertRule::getIotId, Constants.ALL_IOT_ID)
                    .eq(AlertRule::getStatus, Constants.STATUS_ENABLE)
                    .list();
            // 4.1.2 查询报数据对应的设备自己关联的报警规则列表
            List<AlertRule> alertRuleListForDevice = this.lambdaQuery()
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    .eq(AlertRule::getIotId, deviceData.getIotId())
                    .eq(AlertRule::getStatus, Constants.STATUS_ENABLE)
                    .list();

            // 4.1.3 合并两个集合（物联网通用逻辑，包含单个产品特殊规则）
            Set<AlertRule> alertRulesSet = CollUtil.unionDistinct(productAlertRuleList, alertRuleListForDevice);
            if (CollUtil.isEmpty(alertRulesSet)) {
                log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 没有查询到设备关联的报警规则", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId());
                return;
            }

            // 5.遍历每一个规则
            alertRulesSet.forEach(alertRule -> {
                // 5.1第一层校验，判断生效时间
                String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
                if (StrUtil.isEmpty(alertEffectivePeriod)) {
                    log.error("[报警规则校验]获取生效时间失败");
                    return;
                }
                String[] periodTime = alertEffectivePeriod.split("~");
                List<String> periodList = Arrays.asList(periodTime);
                LocalTime startTime = LocalTime.parse(periodList.get(0));
                LocalTime endTime = LocalTime.parse(periodList.get(1));
                LocalTime alarmTimeLocalTime = alarmTime.toLocalTime();
                // 不在规则时间内
                if (alarmTimeLocalTime.isBefore(startTime) || alarmTimeLocalTime.isAfter(endTime)) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{},设备上报数据时间不在报警规则生效时间段内", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName());
                    return;
                }
                // 5.2第二层校验，判断数据阈值，未达到删除redis报警数
                // 获取运算符
                String operator = alertRule.getOperator();
                // 获取阈值
                Double value = alertRule.getValue();
                if (StrUtil.isEmpty(operator) || value == null) {
                    log.error("[报警规则校验]获取阈值失败");
                    return;
                }

                // 报警次数的缓存key,分层
                String countKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_COUNT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());

                Double dataValue = Double.valueOf(deviceData.getDataValue());
                if (("<".equals(operator) && (dataValue >= value)) || (">=".equals(operator) && (dataValue < value))) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 现在值：{}, 未到到告警规则阈值：{}", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), dataValue, value);
                    // 未达到删除redis报警次数（因为要求连续性）
                    redisTemplate.delete(countKey);
                    return;
                }
                // 5.3第三层校验，判断沉默周期(沉默周期不归零，不能重复提交)
                // 沉默周期key和value
                String silentKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_SILENT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                String silentValue = redisTemplate.opsForValue().get(silentKey);
                if (StrUtil.isNotBlank(silentValue)) {
                    Long ttl = redisTemplate.getExpire(silentKey);
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 还在沉默周期内，无需处理，沉默周期还剩余{}秒 ", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), ttl);
                    return;
                }
                // 5.4第四次校验，判断持续周期(小于最大上限次数不能提交)
                Integer duration = alertRule.getDuration();
                // 设置报警次数缓存，不存在则初始化0自增为1，存在则自增1
                Long countValue = redisTemplate.opsForValue().increment(countKey);
                if (countValue < duration) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 还在持续周期内（未达上限），现在次数：{}，上限次数：{} ", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), countValue, duration);
                    return;
                }

                // 设置沉默周期缓存和有效期
                Integer alertSilentPeriod = alertRule.getAlertSilentPeriod();
                redisTemplate.opsForValue().set(silentKey, "liyi", alertSilentPeriod, TimeUnit.MINUTES);

                // 准备提交数据，先清空报警次数
                redisTemplate.delete(countKey);

                // 6.查询对应员工和管理员id
                // 6.1 判断是不是老人异常数据还是设备异常数据
                Integer alertDataType = alertRule.getAlertDataType();
                if (alertDataType == null) {
                    log.error("[报警规则校验]获取异常数据类型失败");
                    return;
                }
                // 如果是老人数据异常
                List<Long> userIds = null;
                if (alertDataType == 0) {
                    // 判断是随身设备
                    if (deviceData.getLocationType() == 0) {
                        userIds = deviceMapper.selectNuringIdsByMoveIotId(deviceData.getIotId());
                    } else {
                        // 固定设备
                        userIds = deviceMapper.selectNuringIdsByStopIotId(deviceData.getIotId());
                    }
                } else {
                    // 设备数据异常
                    userIds = sysRoleMapper.selectUserIdsByRoleName(roleRepair);
                }
                // 查询超级管理员id
                List<Long> adminIds = sysRoleMapper.selectUserIdsByRoleName(roleAdmin);

                // 合并id集合
                Set<Long> userAndAdminIds = CollUtil.unionDistinct(adminIds, userIds);
                if (CollUtil.isEmpty(userAndAdminIds)) {
                    log.error("[报警规则校验]没有查询到任何用户ID，设备ID：{}", deviceData.getIotId());
                    return;
                }

                // 7. 构造报警数据,为用户批量保存报警数据
                //报警原因，格式：功能名称+运算符+阈值+持续周期+沉默周期
                String alertReasonTemplate = "功能：{}{}{}，持续周期：{}，沉默周期：{}";
                String alertReason = CharSequenceUtil.format(alertReasonTemplate, alertRule.getFunctionName(), alertRule.getOperator(),
                        alertRule.getValue(), alertRule.getDuration(), alertRule.getAlertSilentPeriod());


                List<AlertData> alertDataList = userAndAdminIds.stream().map((Function<Long, AlertData>) userId -> {
                    AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
                    alertData.setId(null);
                    alertData.setAlertRuleId(alertRule.getId());
                    alertData.setAlertReason(alertReason);
                    alertData.setType(alertDataType);
                    alertData.setStatus(Constants.STATUS_UNPROCESSED);
                    alertData.setUserId(userId);
                    return alertData;
                }).collect(Collectors.toList());


                // 批量新增报警数据
                alertDataService.saveBatch(alertDataList);
                log.info("[报警规则校验]新增报警数据完毕。设备ID：{}，报警信息：{}", deviceData.getIotId(), alertReason);
            });
        });

    }
}
